package com.hyt.it.ogt.kq.service.gov.schedule.utils;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;

public class ReflectionUtils {
    private static final String UNSUPPORTED_MEMBER_TYPE_EXCEPTION_MESSAGE = "Unsupported member type [%s]";
    /**
     * Specifying a reflection operation should be performed recursive.
     */
    public static final boolean RECURSIVE = true;
    /**
     * Specifying a reflection operation should not be performed recursive.
     */
    public static final boolean NOT_RECURSIVE = false;

    /**
     * Returns an {@link Iterable} of all the fields declared on the given class and
     * its super classes. The iterator
     * will always return fields declared in a subtype before returning fields
     * declared in a super type.
     *
     * @param clazz the class to return fields for
     * @return an {@code Iterable} providing access to all declared fields in the
     *         class hierarchy
     */
    public static Iterable<Field> fieldsOf(Class<?> clazz) {
        return fieldsOf(clazz, NOT_RECURSIVE);
    }

    /**
     * Set the {@code field} of {@code object} to a certain {@code value}. If
     * necessary, the field is made accessible,
     * assuming the security manager allows it.
     *
     * @param field  The field to set {@code value} on
     * @param object The object to set the {@code value} on {@code field}
     * @param value  The value to set on {@code field}
     * @param <T>    The type of the {@code value}
     */
    public static <T> void setFieldValue(Field field, Object object, T value) {
        try {
            field.set(object, value);
        } catch (IllegalAccessException ex) {
            throw new IllegalStateException("Unable to access field for setting.", ex);
        }
    }

    /**
     * Set the {@code field} of {@code object} to a certain {@code value}. If
     * necessary, the field is made accessible,
     * assuming the security manager allows it.
     *
     * @param field  The field to set {@code value} on
     * @param object The object to set the {@code value} on {@code field}
     * @param value  The value to set on {@code field}
     * @param <T>    The type of the {@code value}
     */
    public static <T> void setFieldValue(String fieldName, Object object, T value) {
        try {
            Field field = object.getClass().getDeclaredField(fieldName);
            field.set(object, value);
        } catch (IllegalAccessException ex) {
            throw new IllegalStateException("Unable to access field for setting.", ex);
        } catch (NoSuchFieldException ex) {
            throw new IllegalStateException("Unable to find field for setting.", ex);
        }
    }

    @SuppressWarnings("unchecked")
    public static <T> T newInstanceClassNoParams(Class<T> clazz) {
        try {
            for (Constructor<?> construct : clazz.getConstructors()) {
                if (construct.getParameterCount() == 0) {
                    T instance = (T) construct.newInstance();
                    return instance;
                }
            }
            throw new RuntimeException(
                    "newInstanceClass error :" + clazz.getName() + " can't find empty new construct !");

        } catch (Exception e) {
            throw new RuntimeException("newInstanceClass error", e);
        }
    }

    /**
     * Returns an {@link Iterable} of all the fields declared on the given class.
     * <p>
     * Will include the given {@code clazz}' super classes if {@code recursive} has
     * been set. The iterator will always
     * return fields declared in a subtype before returning fields declared in a
     * super type.
     *
     * @param clazz     the class to return fields for
     * @param recursive defining whether fields should be found recursively on super
     *                  classes as well
     * @return an {@code Iterable} providing access to all declared fields in the
     *         class, including the hierarchy if
     *         {@code recursive} was set
     */
    public static Iterable<Field> fieldsOf(Class<?> clazz, boolean recursive) {
        List<Field> fields = new LinkedList<>();
        Class<?> currentClazz = clazz;
        do {
            fields.addAll(Arrays.asList(currentClazz.getDeclaredFields()));
            currentClazz = currentClazz.getSuperclass();
        } while (currentClazz != null && recursive);
        return Collections.unmodifiableList(fields);
    }

    /**
     * Returns the value of the given {@code field} in the given {@code object}. If
     * necessary, the field is
     * made accessible, assuming the security manager allows it.
     *
     * @param field  The field containing the value
     * @param object The object to retrieve the field's value from
     * @return the value of the {@code field} in the {@code object}
     * @throws IllegalStateException if the field is not accessible and the security
     *                               manager doesn't allow it to be
     *                               made accessible
     */
    @SuppressWarnings("unchecked")
    public static <R> R getFieldValue(Field field, Object object) {
        try {
            // field.setAccessible(true);
            R result = (R) field.get(object);
            return result;
        } catch (IllegalArgumentException | IllegalAccessException ex) {
            throw new IllegalStateException("Unable to access field for getting.", ex);
        }
    }

    public static Method getMethodByName(String methodName, Object object) {
        for (Method method : object.getClass().getDeclaredMethods()) {
            if (method.getName().equals(methodName)) {
                return method;
            }
        }
        throw new IllegalStateException("Unable to find method " + methodName);
    }

    public static Method getClassMethodByName(String methodName, Class<?> cls) {
        for (Method method : cls.getDeclaredMethods()) {
            if (method.getName().equals(methodName)) {
                return method;
            }
        }
        throw new IllegalStateException("Unable to find method " + methodName);
    }

    public static <R> R getFieldValueFromMethod(Field field, Object object) {
        String methodName = "get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);
        try {
            return ReflectionUtils.getMemberValue(object.getClass().getDeclaredMethod(methodName), object);
        } catch (NoSuchMethodException | SecurityException ex) {
            throw new IllegalStateException("Unable to access method " + methodName + "  for getting.", ex);
        }
    }

    public static void setFieldValueFromMethod(String fieldName, Object object, Object value) {
        String methodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
        try {
            for (Method method : object.getClass().getDeclaredMethods()) {
                if (method.getName().equals(methodName)) {
                    method.invoke(object, value);
                    return;
                }
            }
        } catch (RuntimeException | IllegalAccessException | InvocationTargetException ex) {
            throw new IllegalStateException("Unable to access method " + methodName + "  for setting.", ex);
        }
    }

    /**
     * Returns the value of the given {@code member} in the given {@code object},
     * either by returning {@link Field}
     * value or invoking the method. If necessary, the member is made accessible,
     * assuming the security manager allows
     * it. Supported members are {@link Field} and non-void {@link Method} without
     * parameters.
     *
     * @param member the member containing or returning the value
     * @param target the object to retrieve the member's value from
     * @return the value of the {@code member} in the {@code object}
     * @throws IllegalStateException if the member is not supported
     */
    public static <R> R getMemberValue(Member member, Object target) {
        if (member instanceof Field) {
            return ReflectionUtils.getFieldValue((Field) member, target);
        } else if (member instanceof Method) {
            return ReflectionUtils.invokeAndGetMethodValue((Method) member, target);
        }
        throw new IllegalStateException(
                String.format(UNSUPPORTED_MEMBER_TYPE_EXCEPTION_MESSAGE, member.getClass().getName()));
    }

    /**
     * Invokes and returns the return value of the given {@code method} in the given
     * {@code object}. If necessary, the
     * method is made accessible, assuming the security manager allows it.
     *
     * @param method the method to invoke
     * @param object the target object the given {@code method} is invoked on
     * @return the resulting value of invocation of the {@code method} in the
     *         {@code object}
     * @throws IllegalStateException if the method is not accessible and the
     *                               security manager doesn't allow it to be
     *                               made accessible
     */
    @SuppressWarnings("unchecked")
    public static <R> R invokeAndGetMethodValue(Method method, Object object) {
        try {
            return (R) method.invoke(object);
        } catch (IllegalAccessException | InvocationTargetException ex) {
            throw new IllegalStateException("Unable to access method for invocation.", ex);
        }
    }

    /**
     * Invokes the given {@code method} in the given {@code object}. If necessary,
     * the method is made accessible, assuming the security manager allows it.
     * 如果 fileName 存在于object的子类，则以 . 分割，获取子类的方法
     * 
     * @param method the method to invoke
     * @param object the target object the given {@code method} is invoked on
     * @throws IllegalStateException if the method is not accessible and the
     *                               security manager doesn't allow it to be
     *                               made accessible
     */
    @SuppressWarnings("unchecked")
    public static <R> R getFieldValueByName(String fieldName, Object object) {

        try {
            String[] fileNameLists = fieldName.split("\\.");
            if (fileNameLists.length > 1) {
                Object obj = object;
                for (int i = 0; i < fileNameLists.length; i++) {
                    obj = getFieldValue(obj.getClass().getDeclaredField(fileNameLists[i]), obj);
                    if (obj == null) {
                        return null;
                    }
                }
                return (R) obj;
            } else {
                Field field = object.getClass().getDeclaredField(fieldName);
                return ReflectionUtils.getFieldValue(field, object);
            }
        } catch (NoSuchFieldException | NullPointerException | SecurityException ex) {
            throw new IllegalStateException("Unable to access field " + fieldName + "  for getting.", ex);
        }
    }

    public static Field getClassFieldIgnoreCase(String fieldName, Class<?> cls) {
        for (Field item : cls.getDeclaredFields()) {
            if (item.getName().toLowerCase().equalsIgnoreCase(fieldName.toLowerCase())) {
                return item;
            }
        }
        return null;
    }

    public static List<Field> getClassFieldIgnoreCase(List<String> fieldNames, Class<?> cls) {
        List<Field> fields = new ArrayList<>();
        fieldNames.forEach(item -> {
            Field field = getClassFieldIgnoreCase(item, cls);
            if (!Objects.isNull(field)) {
                fields.add(field);
            }
        });
        return fields;

    }

}
